===== Build 447 v0.9.2 =====

## Sleep WooHoo / bed interactions: pie menu reliability and role clarity

### Background

- Reports indicated a **broken or sparse sleep-related pie menu** on Sims who were asleep (missing entries, empty queue under the Kinky World branch, interference when other script mods added entries in the same pie subtree).
- With **Enable Dominant/Submissive Relation** enabled, the menu could fail or present inconsistent options because naming and relationship branches mixed dom/sub with the sleep WooHoo flow.

### Dom/sub vs sleep WooHoo

- **Dominant/submissive** behavior remains available where it was already used outside this flow.
- **Sleep WooHoo** no longer ties interaction labels or relationship handling to the dom/sub setting: names and "TryWooHooInBed" relationship logic use the **trait-based** path unless the player’s role is explicitly **locked** by the new sleep interaction flags (see below).

### Active and Passive entries (always visible)

- For **vaginal**, **handjob**, and **oral** WooHoo on a sleeping Sim (non-rape categories), the pie menu now offers **two separate interactions** at all times:
  - **Active** — uses the primary interaction name + category tuning (actor-sub animation path off as appropriate for that definition).
  - **Passive** — uses the “sub” interaction name key ("InteractionNameSub…" family where applicable) + category, with **"UseActorSubAnimationPath"** so clip selection matches the passive role.
- **Incest** variants mirror the same pattern with dedicated passive definition classes (cannot inherit from sealed "Definition_Incest"; passive incest classes inherit **"Definition"** directly with the correct tuning flags).
- **Rape** remains a **single** interaction definition (no duplicate active/passive pair).
- **"WooHooSleepingSimOnBed.Run()"** (on "KWBedSleep") sets:
  - "WooHooCategory"
  - **"mSleepWooHooPlayerRoleLocked"** — player’s active vs passive choice is fixed for the upcoming bed flow
  - **"mSleepWooHooUseActorSubPath"** — whether this choice maps to the actor-sub animation path (false for rape; false when category is "None"; otherwise driven by passive vs active)

### "KWBedSleep" integration

- New fields: **"mSleepWooHooPlayerRoleLocked"**, **"mSleepWooHooUseActorSubPath"**.
- **"TryAddInteractions"**, **"Cleanup"**, **"StartWooHoo"** (and related removal lists): include all new passive definitions and singletons so they register and tear down cleanly.
- **"TryWooHooInBed"**: when **"mSleepWooHooPlayerRoleLocked"** is true, **"mActorSub"** / **"mTargetSub"** are **forced** from **"mSleepWooHooUseActorSubPath"** so stage parameters match the pie-menu choice; when not locked, prior trait-based (and non–sleep-dom/sub) logic applies.
- **"Cleanup"**: resets both flags after the situation ends.

### Files touched

- "Oniki.Interactions/WooHooSleepingSimOnBed.cs"
- "Oniki.Interactions/KWBedSleep.cs"

### Localization note (STBL)

- Ensure STBL coverage for passive labels that mirror active strings with a **"Sub"** logical key (e.g. **"InteractionNameSubHandjob"**, **"InteractionNameSubOraljob"**, and incest counterparts if those keys are used in code) so passive rows do not show raw keys in-game. Full keys for packaging follow **"Oniki.KinkyMod.<logicalKey>>"** as usual.

---

## High School ("SchoolLocation"): localized “no school lot registered” warning

### Issue (before)

- In **"SchoolLocation.AssignStudents()"**, when **"__locations.Count == 0"** after the auto-registration attempt from a lot typed **"HighSchool"**, the code called **"UITools.Warning("SchoolLocation.AssignStudents:NoSchoolRegistered")"** with a **literal string**.
- **"UITools.Warning"** forwards text to **"StyledNotification"** **without** going through **"UITools.Localize"**, so players saw the **raw token** and no STBL lookup occurred for **"Oniki.KinkyMod.…"**.

### Change

- **"Oniki.Careers/SchoolLocation.cs"** — same branch now uses:
  - **"UITools.Warning(UITools.Localize("SchoolLocation.AssignStudents:NoSchoolRegistered"));"**
- **Logical key (code)** is unchanged: **"SchoolLocation.AssignStudents:NoSchoolRegistered"**.
- **Runtime / STBL string key** (full key for FNV64 / package editors): **"Oniki.KinkyMod.SchoolLocation.AssignStudents:NoSchoolRegistered"**.
- If the STBL row is **missing**, **"Localize"** still falls back to showing the short logical key (same as before from a player’s perspective, but the code path is now correct for packaged strings).
- More strings for Clubs creation issues were added + more marginal notifications that were incomplete or missing an STBL actual key.


---

===== Build 447 v0.9.3 =====

### Wash Hands ("Sink_WashHands"): NullReferenceException with KW disabled / no "SimData"

#### Issue

- **"Oniki.Interactions.Sink_WashHands.Run()"** treated **"SimData.Get(Actor.SimDescription)"** as always non-null. When **Kinky World is disabled** in the mod settings, **"SimData.Get"** returns **"null"**, but the patched **"Sink.WashHands.Singleton"** still runs **"Sink_WashHands"**. For **user-directed** washes, **"Test()"** does not evaluate the autonomous cooldown branch, so the interaction can start and then throw **"NullReferenceException"** in **"Run()"** (post-loop KW cleanup and/or **"AddCooldown"**), interrupting the wash animation. NRaas ErrorTrap logs showed the failure inside **"Sink_WashHands:Run"**.

#### Change

- **"Oniki.Interactions/Sink_WashHands.cs"**
  - **"Run()"**: guard **"HasButtPlugDirty" / "HasPeriodCupDirty"** (and related messages) with **"if (simData != null)"**; call **"AddCooldown"** only when **"SimData.Get"** is non-null at exit.
  - **"Test()"**: for autonomous washes, use a local **"SimData"** and require **"sd != null && sd.OffCooldown("washhands", null)"** so **"OffCooldown"** is never invoked on **"null"** when KW is disabled.

#### Playtesting focus

- Wash hands with **KW enabled** and **KW disabled** (user-directed and, if possible, autonomous): interaction should complete without script error.
- Optional regression: butt plug / menstrual cup “cleaned at sink” messages still fire when **"SimData"** exists and the Sim has dirty items.

---

### Keyboard shortcuts ("kKeyboardShortcuts"): Delete / Esc broken in Buy Mode

#### Issue

- With **"kKeyboardShortcuts = True"** (tuning on **"Oniki.KinkyMod"**, e.g. from **"ONIKI_KinkySettings.package"**), some vanilla Buy Mode keys stopped working (**Delete** to remove objects, **Esc**, etc.). With **"False"**, behavior returned to normal.
- The packaged TRIG maps (**F5** >> "KWSaveGame", **Q** >> "KWNextStage", **E** >> "KWChangePosition") only bind those keys; they do not target Delete/Esc.

#### Cause

- **"KWTrigger.Start()"** registered an extra **"AddTriggerHook("script_ingame", TriggerActivationMode.kPermanent, int.MaxValue)"** on **"SceneMgrWindow"**.
- Vanilla **"SceneMgrWindow"** already owns the **"script_ingame"** map with **"TriggerActivationMode.kManual"** and priority **0** for forwarding trigger events between game and user tools (Build/Buy). A second hook on the same map with **"kPermanent"** and maximum priority interfered with that pipeline.

#### Change

- **"Oniki.Interactions/KWTrigger.cs"** — removed the **"script_ingame"** hook; kept only the named hooks **"KWNextStage"**, **"KWChangePosition"**, **"KWSaveGame"** (aligned with legacy **"Build 442"**-era "KWTrigger" in repo extracts).

#### Playtesting focus

- Enable **"kKeyboardShortcuts"** via tuning; confirm **F5 / Q / E** still work in Live when appropriate, and **Delete / Esc** work again in **Buy Mode** (and Build Mode if you use the same shortcut set).

---

### Service NPCs (Mail / Pizza / Repairman / Maid): "Clean" vs "ValidForCurrentWorld" and vanilla mail restore

#### Issue

- **"KWMailCarrier.Start()"** (and similarly **repairman**, **maid**, **pizza**) called **"KWServices.Clean<…>>()"** *before* **"ServiceNPCSpecifications.ValidForCurrentWorld"**. If that check was **false** (e.g. specs not ready, world name still **Undefined** during load), the **EA service instance was destroyed** and **no KW replacement** was created >> **no mail carrier** (and same class of failure for other services when the pattern applied).
- With **LoversLab Mail Carrier OFF**, **"KWMailCarrier.Start()"** did nothing after **"Stop()"**, so **"MailCarrier.Create()"** was never used to **re-register** the EA deliverer after removing **"KWMailCarrier"** in-session.

#### Change

- **"Oniki.Services/KWMailCarrier.cs"**
  - **"Start()"**: evaluate **"ValidForCurrentWorld(ServiceType.MailCarrier)"** *before* **"Clean<MailCarrier>>()"**; when **"KWMailCarrier"** is **off** but the mod is enabled, call **"MailCarrier.Create()"** so the vanilla deliverer is ensured.
  - **"ApplyRuntimeToggle()"**: on disable, **"Stop()"** then **"Start()"** so vanilla mail is restored immediately in the same session.
- **"Oniki.Services/KWPizzaDelivery.cs"** — run **"Clean<PizzaDelivery>>()"** only **after** **"ValidForCurrentWorld(PizzaDelivery)"** passes (same ordering fix).
- **"Oniki.Services/KWRepairman.cs"**, **"Oniki.Services/KWMaid.cs"** — **"Clean"** moved **inside** the **"ValidForCurrentWorld"** branch for the respective service type.

#### Playtesting focus

- **Mail:** LoversLab option **ON** and **OFF**; postman appears when expected; toggle OFF in-game then verify mail still works after without full reload.
- **Pizza / repairman / maid:** world loads where those services apply; no “missing service” after load when the world becomes valid a moment later.

---

### KW Mail Carrier situation: restore EA **"PutMail"** with guards (delivery + KW hangout)

#### Issue

- **"KWMailCarrierSituation.KWRouteToLot"** only called **"OnServiceStarting()"** then **"KWHangAroundBeforeLeaving"**, so the postman **never** ran **"Mailbox.PutMail"** >> no EA delivery step when LoversLab mail was **enabled**.

#### Change

- **"Oniki.Situations/KWMailCarrierSituation.cs"**
  - **"KWRouteToLot.Init"**: mirror **"MailCarrierSituation.RouteToLot"** — find mailbox / post box, **"ForceSituationSpecificInteraction"** with **"Mailbox.PutMail.Singleton"**, **"AngryMailmanDay"** when appropriate.
  - **Guards before forcing "PutMail":** mailbox exists and not destroyed; **"HasInvisibleMail"**; **"Service.IsServiceRequested(Lot)"**; if **"PutMail"** cast is **null**, treat as failure (**"OnDeliveryFailed"**).
  - **Callbacks:** same EA branches as vanilla (**"DogBark"**, **"EventTracker.SendEvent(kGetMailSuccess, …)"** for Social Networking blog), then **"KWHangAroundBeforeLeaving"** on successful delivery (KW behavior after mail).
  - **Usings added:** **"Sims3.Gameplay.CAS"**, **"Sims3.Gameplay.EventSystem"**.

#### Playtesting focus

- With LoversLab mail **ON**: bills / online store mail / other invisible-mail flows still deliver; no null-mailbox soft resets; postman still does KW social phase after delivery when conditions match.
- With mail **OFF**: unchanged vanilla **"MailCarrierSituation"** path.

---

### Romantic socials ("KinkySocialInteraction"): PDA visibility vs **"JealousyLevel.None"**

#### Issue

- With **KW "JealousyLevel = None"**, WooHoo-side jealousy logic was already disabled, but **romantic socials** that complete through **"KinkySocialInteraction"** still called **"Relationship.UpdateRomanceVisibilityForPdaIfNecessary(Actor, Target, base.ActionData.JealousyLevel)"**, passing the **per-interaction** EA jealousy level from **"ActionData"** instead of the **player’s KW setting**.
- That could still drive **EA PDA / “witness” style reactions** (e.g. thought balloons near an official partner) even when the user expected **no jealousy** from KW.

#### Change

- **"Oniki.Interactions/KinkySocialInteraction.cs"**
  - When **"Main.Settings.JealousyLevel == JealousyLevel.None"**, pass **"JealousyLevel.None"** into **"UpdateRomanceVisibilityForPdaIfNecessary"**; otherwise keep **"base.ActionData.JealousyLevel"** (unchanged for Low / Medium / High).

#### Playtesting focus

- **"JealousyLevel = None"**: complete a **KW romantic social** accepted with the partner nearby — **no** extra PDA/jealousy balloon from this callsite; KW jealousy remains off as before.
- **"JealousyLevel" >> None**: confirm PDA visibility behavior is **unchanged** vs prior build for the same interactions.

---

### World load / ErrorTrap: **"StackOverflowException"** in **"KinkySkill.AddSkill"** / **"WooHooSkill.CreateSkillJournalInfo"**

#### Issue

- ErrorTrap logs showed **"System.StackOverflowException"** (interpreter max stack depth) during **"KinkyMod.PostLoadFixup"** / **"World.OnWorldLoadFinished"**, with repeating frames: **"KinkySkill.AddSkill"** >> **"SimData.Get(description, false)"** >> **"WooHooSkill" clone / "CreateSkillJournalInfo"** >> **"KinkySkill+LifetimeOpportunity" ctor** >> **"UITools.Localize(..., SimDescription)"** >> **"SimTools.LocalizationFemaleForms"** >> **"SimData.Get(..., false)"** again.
- **"SimData.Get(..., noVolatile: false)"** creates **"VolatileSimData"** and calls **"KinkySkill.AddSkill<WooHooSkill>>"** before the skill is registered; journal setup localizes strings and **"LocalizationFemaleForms"** called **"Get(..., false)"** again, re-entering **"AddSkill"** until stack overflow — severe stutter / hang on load (e.g. large worlds).

#### Change

- **"Oniki.Utilities/SimTools.cs"** — **"LocalizationFemaleForms(Sim)"** and **"LocalizationFemaleForms(SimDescription)"** now call **"SimData.Get(..., noVolatile: true)"** so this path **does not** allocate volatile SimData or re-enter **"AddSkill"** during that bootstrap. If SimData is not yet available, the method falls back to **"Sim.IsFemale" / "SimDescription.IsFemale"** (futanari-specific wording is applied only after SimData exists on the normal **"Get(..., false)"** path elsewhere).

#### Playtesting focus

- Load a save with many human Sims (e.g. Bridgeport): no ErrorTrap spam for this overflow; post-load performance acceptable.
- **Futanari:** after Sim is fully initialized, UI strings should still respect futanari where SimData is read with the usual non-volatile path; brief bootstrap edge only.

---


===== Build 448 v0.9.3 =====

### Futanari / Shemale — comprehensive update

#### Summary

- Primary release line for **futanari (shemale) gameplay, body, localization, and womb-dialog fixes** carried forward from the **448 / v0.9.3** wave (working notes: "Old documents/Shemales").
- Scope spans **CAS/transform body apply**, **erection morph split**, **grammatical gender in UI** ("MB"/"FB" vs "MA"/"FA"), **incest cum-in-womb key routing** for futa mother >> daughter, **diagnostic logging**, and **exhibition / morning-wood reaction** call sites.
- **Install note (content, not code):** distorted or “invisible” futa erection in WooHoo/CAS is often **CMAR slider package version** — CMAR “morphing penis” sliders need **11/21+** for female-penis geometry; outdated sliders can look correct in KW logs ("KW_Erection", blend layers) but wrong in viewport.

#### Penis erection morph (cut vs uncut)

##### Issue

- **"MorphTools.SetPenisErection"** applied a **futanari-only override**: when foreskin on the builder was ~0, the code forced **all** scale onto **uncut** ("toUncut = scale", "toCut = 0"), assuming CMAR female bottoms without a useful foreskin morph.
- In CAS, **standard erection max + uncut 0** looked correct; **uncut-only max** looked deformed — matching in-game WooHoo distortion for futa.
- **Male Sims were not affected** — the override ran only with **"futanariCmarLowerBody" / "IsFutanari"**.

##### Change

- **"Oniki.Utilities/MorphTools.cs"** — removed both futa-only overrides ("SimBuilder" and "SimOutfit"/"SimDescription" overloads). All Sims now use the shared foreskin split: "toCut = (1 - foreskin) * scale", "toUncut = foreskin * scale".

#### Transform to shemale: stable body apply (no “half futa” state)

##### Issue

- After **"Sim_TransformToShemale"** / **"CreateShemale"**, **"IsFutanari"** could be **true** while **Get Naked**, autonomous nude, and WooHoo still failed until a **second** transform or revert.
- **"KWInteractionLog"** with CAS-futa diagnostics showed repeated  
  **"[KW-CAS-FUTA] ... ApplyShemaleBody_SimOutfit outcome=skip_not_lower_naked_preset"**.
- **"ApplyShemaleBody"** required **"OutfitHasNakedPart(LowerBody)"** against hardcoded **"kBottomNakedKeys"** (canonical EA teen/adult/elder nude instance IDs). Saves/replacements can use a **different** lower instance that is still “nude” in play but **not** in that list >> **zero wardrobe slots** received the shemale merge >> unstable **flags vs outfit** state.

##### Change

- **"Oniki.Utilities/OutfitTools.cs"**
  - **"OutfitLowerBodyQualifiesForShemaleBody(SimOutfit)"**: qualifies if existing **"OutfitHasNakedPart(LowerBody)"** **or** any **"LowerBody"** CASPart has **"(CategoryFlags & 1) != 0"** (engine naked bit, aligned with **"__GetNakedFlags"**). Does **not** widen to all lower-body parts (jeans/skirts stay excluded).
  - **"ApplyShemaleBody(SimDescription, SimOutfit, …)"** uses this gate; skip log renamed to **"skip_lower_not_recognized_as_nude"**.
- **"Oniki.Gameplay/OutfitManager.cs"**
  - **"ApplyShemaleBody()"** returns **"int"** (outfits actually updated); **"-1"** if lock invalid or live Sim missing. Private **"ApplyShemaleBodyToOutfitList(...)"** mirrors category/maternity loop used elsewhere.
- **"Oniki.Utilities/SimTools.cs"**
  - **"CreateShemale"**: if **"ApplyShemaleBody() <= 0"** >> **"RemoveFlags(Male)"**, **"return false"**, player **"UITools.FeedbackNotify"** with transform-failed key (see STBL below).
  - **"SetGender"**: same rollback on female>>futa and **"Futunari"** default paths; restores **"simGenderMaskAtEntry"** on failure.
- **"Oniki.Gameplay/SimData.cs"** — post-CAS / init: if apply count **"<= 0"** and diagnostics on >> **log only** (no automatic rollback on load/CAS).

**STBL (transform failure feedback)**

- "Oniki.KinkyMod.CasFutanari.ShemaleTransformFailed"
- *(hash: add to language package when deploying STBL)*
- "EN: Transformation to shemale has failed. Please try again."

#### CAS / futanari body diagnostics

- **"Log.ShouldCasFutanariBodyDiagnostics()"** — gated by existing **"SimOutfitLogging"** + **"EnableGlobalBuffer"**; traces written via **"Log.WriteCasFutanariBodyDiag"** >> **"KWInteractionLog_"** prefix **"[KW-CAS-FUTA]"**.
- Instrumented paths: **"OutfitTools.ApplyShemaleBody"**, **"OutfitManager.ApplyShemaleBody"**, **"SimTools.CreateShemale" / "RemoveShemale" / "SetGender"**, **"SimData"** post-CAS/init, debug **"Sim_TransformToShemale"**.
- Use when diagnosing teen vs YA, **"CreatedSim == null"**, outfit cache, part counts, and skip reasons.

#### Localization — female grammatical forms ("MB"/"FB") for futanari

##### Core issue

- Futanari remain **"Sim.IsMale" / "SimData.IsMale"** for mechanics (penis, WooHoo) but should use **feminine** token branches in gendered languages (e.g. Italian *nuda* not *nudo*).
- **"SimTools.LocalizationFemaleForms"** already returns **"true"** for **"IsFutanari"**, else **"IsFemale"**.

##### UITools core overloads

- **"Oniki/UITools.cs"** — overloads **"Localize(string, Sim)"**, **"Localize(string, SimDescription)"**, and two-Sim variants now pass **"LocalizationFemaleForms(...)"** instead of raw **"IsFemale"**, so all call sites on those paths pick **"FB"** for futa without per-file edits.
- Call sites that already pass explicit **"Localize(key, bool isActorFemale, bool isTargetFemale, …)"** are unchanged by this layer.

##### Morning wood — daughter >> parent (futa parent)

- **"Oniki.Gameplay/NakedBroadcaster.cs"** — **"LocalizeDaughterMorningWoodParentReaction"**: actor = **daughter** (speaker, "MA"/"FA"), target = **parent** broadcaster ("MB"/"FB", including futanari mother).
- Keys: "moodlet/morningwood/reaction:daughter_attracted_aroused_1", "daughter_attracted_aroused_0", "daughter_attracted_1", "daughter_attracted_0", "daughter_notattracted".
- **STBL packer:** use **"{MB…}" / "{FB…}"** for the **parent** role and **"{MA…}" / "{FA…}"** for the **daughter**; fix Italian lines that hardcode *è nudo* when the subject is the parent.

##### Exhibition / Show reactions (nude exhibitor = "MB"/"FB")

- **"NakedBroadcaster.ReactionFeedback_*"** and related Show interactions were updated so **"Localize(..., LocalizationFemaleForms(speaker), LocalizationFemaleForms(exhibitor), …)"** agrees adjectives with the **nude Sim**, not only the speaker.
- **Files:** "Oniki.Interactions/ShowBottom.cs", "Flash.cs", "ShowBoobs.cs", "ShowBra.cs", "ShowFeet.cs", "ShowPanties.cs", "PullDownClothes.cs" (and existing "NakedBroadcaster" paths that already used explicit forms).
- **STBL packer:** wherever **"{1.String}"** is the relationship name of the **nude** Sim (e.g. *mia madre*), replace **"{MA.nudo}{FA.nuda}"** on that placeholder with **"{MB.nudo}{FB.nuda}"**; penis-size suffix keys ("_TinyPenis", "_SmallPenis", etc.) follow the same exhibitor-target rule.

#### Womb — incest flavor (futa mother >> daughter, "DaddyFuta" keys)

##### Issue

- With **futa mother** donor and **daughter** womb receiver, **DaddyFuta** / cum-in-womb STBL branches could miss when incest flags did not cover “futa mother genealogical donor, daughter womb”.

##### Change

- **"Oniki.Gameplay/Womb.cs"**
  - Extended **"incestDaughterParentLine"** when **"IsDaughterOfForKwIncestFlavor"** fails but daughter-womb futa + male-line KW genealogy applies.
  - **"incestFutaMotherGenealogyDonorDaughterWomb"**: "donorIsFutanari" + parent/child genealogy (not **"IsFatherOf"**-based).
  - **"incestDaddyFutaKeySlot" = "incestDaddy || incestFutaMotherGenealogyDonorDaughterWomb"** for **".Daddy"** branches and **"KwWombIncestFutaDonorParentDaughterCumKey"** (".Daddy" >> ".DaddyFuta" remap).
  - Generic/grandpa/brother/grandma branches gated with **"!incestDaddyFutaKeySlot"**; **".Mommy"** four-way branch keeps **"!incestDaddy"** so EA-female futa mother can still use Mommy/remap where intended.
  - Voice/placeholder fix for **"CumInsideWomb2.Mommy" / "Mommy2"** when **"incestMotherReceiverPenisChildDonor"**.

#### Womb — cum-inside feedback while womb already pregnant

##### Issue

- With an **already pregnant** womb, after **"mSperms.Add"** the code could take the zoo pregnancy branch and **"return"** before any **cum-inside** "DialogBox" (Daddy/Mommy/etc.).

##### Change

- **"Womb.AddSperm"** — removed that early **"return"**; sperm add and non-human pregnancy influence unchanged; **cum feedback dialogs still run** (diagnostic tag: "pregnant_womb=zoo_branch_then_cum_feedback").
- **UX note:** multiple dialogs may fire in one call if fertile/non-fertile branches both match; tune only if in-game noise is excessive.

#### WooHoo feedback logging (diagnostic, optional)

- New setting **"LogWoohooFeedback"** (default **"false"**) in **Misc >> Logging Features**.
- When on (and global buffer enabled): traces **"PostWooHoo_Vaginal"** and **"Womb.AddSperm"** (participants, safe/condom gates, incest/futa/fertility flags, pregnancy branch) to export **"KWWooHooFeedbackLog_"** on quit ("[KW-WOO-FEEDBACK]").
- **Files:** "Oniki/Log.cs", "Oniki/Settings.cs", "Oniki/KinkyMod.cs", "Oniki.UI/OptionSettingMenuMisc.cs", "Oniki.Utilities/WooHooTools.cs", "Oniki.Gameplay/Womb.cs".
- Migration: add-if-missing in **"Upgrade()"** for **"PreviousVersion < 448"** + same-build integrity.

**STBL setting entry**

- "Oniki.KinkyMod.OptionSettings.LogWoohooFeedback"
- *(hash: add to language package when deploying STBL)*
- "EN: Woohoo feedback logging"

#### Known limitations / follow-up (documented, not fixed in 448)

- **"Hospital_Surgery"** (shemale surgery) still calls **"CreateShemale"** without checking the **"bool"** return — failure can still charge / show success; evaluate in a later pass.
- **Futa <-> futa WooHoo:** **"PostWooHoo"** runs per participant; two futa with penis + womb can each trigger **"PostWooHoo_Vaginal"** >> symmetric **"AddSperm"** (design of participant loop, not engine “penetrator” metadata). Fixing needs product rule (dedup, initiator-only, etc.) or richer stage data — see **"KWWooHooFeedbackLog_"** traces when **"LogWoohooFeedback"** is on.
- **Orphan STBL hash "0x95E49F4D1D1D3D29"** (“little slut” in some dumps) does **not** match current **"Oniki.KinkyMod.moodlet/morningwood/reaction:*"** keys in source; prefer documented daughter keys above for packer updates.

#### Playtesting focus (futanari)

- Transform female teen/YA to shemale with **replacement nude lower** (not only canonical "*BottomNude" instance): single transform should enable Get Naked / WooHoo without revert-retry.
- WooHoo erection shape vs CAS sliders (CMAR 11/21+).
- Italian (or gendered language): futa exhibitor >> *nuda*; daughter>>futa-mother morning wood; cum-in-womb Mommy/DaddyFuta with futa mother donor.
- Enable **"SimOutfitLogging"** + **"LogWoohooFeedback"** for regression exports on complex pairs.

#### Files touched (futanari wave)

| Area | File |
|------|------|
| Erection cut/uncut | "Oniki.Utilities/MorphTools.cs" |
| Nude gate + shemale apply | "Oniki.Utilities/OutfitTools.cs" |
| Apply shemale count | "Oniki.Gameplay/OutfitManager.cs" |
| CreateShemale / SetGender | "Oniki.Utilities/SimTools.cs" |
| Post-CAS/init log | "Oniki.Gameplay/SimData.cs" |
| Incest womb / cum / pregnancy feedback | "Oniki.Gameplay/Womb.cs" |
| WooHoo feedback log | "Oniki/Log.cs", "Oniki/KinkyMod.cs", "Oniki/Settings.cs", "Oniki.UI/OptionSettingMenuMisc.cs", "Oniki.Utilities/WooHooTools.cs" |
| Localize >> "LocalizationFemaleForms" | "Oniki/UITools.cs" |
| Morning wood daughter>>parent | "Oniki.Gameplay/NakedBroadcaster.cs" |
| Show / exhibition reactions | "Oniki.Interactions/ShowBottom.cs", "Flash.cs", "ShowBoobs.cs", "ShowBra.cs", "ShowFeet.cs", "ShowPanties.cs", "PullDownClothes.cs" |
| Debug transform trace | "Oniki.Interactions.Debug/Sim_TransformToShemale.cs" |

---


### WooHoo CAS strap-on pie menu (optional, player-directed)

#### Summary

- Added a new **CAS-only strap-on equip/remove** helper in the WooHoo pie path, designed as an opt-in visual correction tool during active scenes.
- Scope is intentionally conservative: this adjusts outfit presentation and does not rewire full WooHoo/PostWooHoo logic.

#### Settings and UI

- New setting: **"EnableWooHooCasStraponPieMenu"** (default "false").
- Added to **Miscellaneous** options menu.
- Migration/integrity coverage added with add-if-missing behavior for existing saves ("PreviousVersion < 448" + same-build integrity).

**STBL setting entry**

- "Oniki.KinkyMod.OptionSettings.EnableWooHooCasStraponPieMenu"

#### Interaction behavior

- Added:
  - **"WooHooEquipStraponCas"**
  - **"WooHooRemoveStraponCas"**
- Manual only (non-autonomous), gated by:
  - mod enabled,
  - global strapon mode not disabled,
  - valid actor/target state for KW scene usage.
- Scene eligibility supports both:
  - regular WooHoo loop contexts ("IWooHooLoop"),
  - sleep WooHoo contexts through "KWBedSleep" activity state.

#### Runtime execution

- Equip/remove uses existing outfit tooling and switches current outfit category/index immediately (delayed no-spin switch path).
- Busy/outfit-invalid states are guarded and fail safely.

### Browse Web for Porn (first wave): generic fallback when no selfies exist

#### Summary

- Extended porn browsing flow so masturbation can still start when there are **no kinky selfies/posts online**, instead of ending in browse-only loops.
- Legacy selfie-driven behavior remains intact and takes precedence when available.

#### Changes

- New setting: **"MasturbateForPornWithoutSelfies"** (default "true").
- Added settings coverage in reset, upgrade, and integrity paths (add-if-missing, preserve existing values).
- Added helper logic in porn browsing flow to detect missing selfie targets and allow generic fallback when enabled.
- Added generic feedback text path:
  - "BrowseWeb.InteractionFeedback.MasturbateGeneric"
  - Added extra time for masturbation animation (60f instead of 15f)

**STBL setting entry**

- "Oniki.KinkyMod.OptionSettings.MasturbateForPornWithoutSelfies"

---



===== Build 449 v0.9.4 =====


### Browse Web for Porn (second wave): fallback and pacing refinements

#### Extended fallback logic

- Generic masturbation fallback is now also allowed when selfies **exist** but do not produce a valid trigger path (e.g. unusable/invalid attraction context), not only when selfies are absent.
- Added fallback evaluator logic so the branch depends on actual target viability, not just selfie count.

#### New feedback coverage

- Added dedicated feedback key for "selfies exist but are not valid for trigger" case:
  - "BrowseWeb.InteractionFeedback.MasturbateFallbackFromBadSelfies"
- Existing no-selfie generic feedback path remains in use.

#### Trigger reliability and animation timing

- Porn popup trigger path moved to deterministic behavior ("kChancePornPopup" raised to 100) for eligible flow windows.
- Eligibility gate aligned to arousal state ("IsAroused") for more consistent start conditions.
- Masturbation phase now has a guaranteed minimum visible duration (60 sim minutes) via dynamic duration-limit extension after transition, preventing too-short outcomes when the browse timer is near completion.



### Spanking wave (full integration)

#### Reliability and queue ownership

- Stabilized accepted ask ->> spank continuation by moving to robust continuation push handling.
- Corrected continuation ownership/instance actor-target mapping to prevent accepted requests ending without animation.
- Added targeted forensic logging for ask/spank push lifecycle and run boundaries (guarded by global buffer toggle).

#### AskToBeSpanked alignment

- Updated teen/incest/age gating to match project toggle policy and incest scope controls.
- Removed legacy teen hard-block branch in result evaluation so allowed teen flows use the same acceptance pipeline as adults.
- Rejection feedback safety fix: removed trait-inferred "RejectedSubmissive" remap and reverted to generic rejection key until explicit reject-reason taxonomy is available.

#### AskToSpank new interaction

- Added **"AskToSpank"** consensual social request flow.
- Manual-only visibility with robust blockers (sleep/woohoo/buff/invalid sim-state checks).
- Acceptance model based on trait/romance/LTR/public-context conditions, then continuation into shared Spank runtime.
- Added STBL-driven interaction name and result feedback keys.

#### Consensual vs punitive path separation

- Introduced dedicated consensual Spank singletons:
  - "Spank.Singleton_Consensual"
  - "Spank.Singleton_Consensual_Incest"
- Kept punitive legacy singletons unchanged.
- Ask-based consensual flows now route to consensual singletons only, avoiding punitive "punishment reason" blockers.

#### Legacy punitive Spank UX refinement

- Updated punitive "Spank" menu policy:
  - available when punishment reason exists, or actor has specific harsh-force traits,
  - otherwise shown greyed out with explanatory tooltip.
- "Dominant" alone no longer bypasses punishment-reason requirement in punitive branch.

#### Completion effects and moodlets

- Added positive completion feedback coverage for successful Spank outcomes.
- Ensured receiver arousal bump on successful completion ("SetHorny" in consensual completion path).
- Added new consensual-positive moodlet:
  - **"GoodSpank"** ("KWBuff" enum + XML buff entry integration),
  - applied only on consensual successful outcomes,
  - punitive moodlet behavior kept unchanged.
  
  ### Dominant satisfaction moodlet (full integration)

#### New buff + resources

- Added new dominant reward moodlet:
  - **"DominantSatisfaction"** ("KWBuff" enum + XML buff entry integration),
  - icon resource: **"moodlet_dominant_satisfaction"**,
  - effect tuning aligned in code/XML: **"EffectValue = 50"**, **"TimeoutLength = 180"**.

#### Consensual Spank trigger

- Added grant path in consensual Spank completion:
  - trigger conditions:
    - Spank request accepted and completed,
    - consensual Spank singleton path,
    - actor has **"Dominant"** trait,
    - target has **"Submissive"** trait.
- On success, actor receives:
  - **"KWBuff.DominantSatisfaction"**
  - origin: **"Origins.FromDominantSpanking"**

#### WooHoo trigger

- Added grant path in WooHoo outcome evaluation:
  - trigger conditions:
    - actor has **"Dominant"** trait,
    - partner has **"Submissive"** trait,
    - WooHoo category is **">= Teasing"**,
    - interaction is **not interrupted**,
    - interaction is **not rape**.
- On success, actor receives:
  - **"KWBuff.DominantSatisfaction"**
  - origin: **"Origins.FromDominantWooHoo"**

#### Origins and runtime notes

- Added dedicated origins in "Oniki.Gameplay/Origins.cs":
  - **"FromDominantSpanking"**
  - **"FromDominantWooHoo"**
- Moodlet grant is guarded to avoid noisy duplicate application in the same WooHoo resolution pass.

### Violence menu and gating alignment

#### Violence menu label and placement

- Rape-related options are now grouped under a dedicated **Violence** menu label:
  - "OptionSettings.MenuViolence.Label"
- In "Global Settings", the **Violence** menu container is positioned at the bottom of the list for clearer separation from non-violent gameplay settings.

#### "EvilSpank" visibility contract

- Added/kept **"EvilSpank"** as a distinct toggle in the **Violence** menu.
- Default for new/initialized settings is **"false"**.
- Legacy punitive Spank visibility respects this toggle:
  - when "EvilSpank" is off, punitive (non-consensual) Spank is blocked for selectable actor-side usage.

#### "Grope" gating (linked to "EnableRape")

- "Grope" pie-menu availability is explicitly tied to **"EnableRape"** ("Main.Settings.GetBool("Rapes")").
- With "EnableRape = OFF", "Grope" does not appear in the pie menu.
- With "EnableRape = ON", "Grope" remains available subject to its existing interaction gates (age/human/posture/autonomy and related checks).

### Post-WooHoo autonomy cooldown alignment ("Seduce" / "Tease")

#### Autonomous social gate update

- Added an explicit autonomous-only guard for:
  - "Oniki.Interactions.Seduce"
  - "Oniki.Interactions.Tease"
- New behavior:
  - when "SimData.IsPostWooHooArousalCooldownActive" is "true", autonomous "Seduce" and "Tease" now fail "TestInternal" immediately.

#### Scope and compatibility

- Change is intentionally scoped to "isAutonomous == true" only.
- Player-directed behavior is unchanged.
- Existing relationship, attraction, trait, incest-scope, and motive gates remain intact.
- This aligns autonomy with the existing post-woohoo cooldown contract (preventing immediate post-woohoo re-trigger spam of "Seduce" / "Tease" in autonomy).